package com.ylr.redis.framework.aspect;

import com.ylr.redis.common.annotation.LockRun;
import com.ylr.base.common.enums.ResultEnum;
import com.ylr.base.common.exception.YlrException;
import com.ylr.base.common.po.user.UserPo;
import com.ylr.base.common.utils.UserUtils;
import lombok.AllArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2023-07-27 15:39:09
 * className: LockRunAspect
 * version: 1.0
 * description:
 */
@Aspect
@Component
@AllArgsConstructor
@ConditionalOnProperty(name = "redisson.enabled", havingValue = "true")
public class LockRunAspect {

    private static final Logger log = LoggerFactory.getLogger(LockRunAspect.class);

    private final RedissonClient redissonClient;

    /**
     * Aop环绕通知方法
     * @param pjp     切面对象
     * @param lockRun 注解对象
     * @return 结果
     */
    @Around("@annotation(lockRun)")
    public Object lockRun(ProceedingJoinPoint pjp, LockRun lockRun) throws Throwable {

        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        String[] argNames = methodSignature.getParameterNames();
        Object[] args = pjp.getArgs();

        // SpringEL语句解析器
        ExpressionParser parser = new SpelExpressionParser();
        // SpringEL上下文
        EvaluationContext ctx = new StandardEvaluationContext();
        for (int index = 0; index < args.length; index++) {
            // 可以进行校验，将参数设置到上下文中
            ctx.setVariable(argNames[index], args[index]);
        }
        UserPo userPo = UserUtils.getUser();
        if (Objects.nonNull(userPo)) {
            // 设置请求用户
            ctx.setVariable("userPo", userPo);
        }

        // 生成key
        String key;
        try {
            key = parser.parseExpression(lockRun.spEL()).getValue(ctx, String.class);
        } catch (ParseException e) {
            log.error("SpringEl语句解析异常，加锁执行失败。message={}", e.getMessage(), e);
            throw new YlrException(ResultEnum.SERVICE_FAIL.getMsg());
        }

        RLock lock = redissonClient.getLock(key);

        if (lockRun.isTryLock()) {
            // 1、尝试加锁
            boolean bool = false;
            try {
                if (lockRun.waitTime() > 0) {
                    // 1.1、等待时长有值
                    if (lockRun.lockTime() > 0) {
                        // 1.1.1、加锁时长有值
                        bool = lock.tryLock(lockRun.waitTime(), lockRun.lockTime(), lockRun.timeUnit());
                    } else {
                        // 1.1.2、不设置加锁时长，使用默认30秒加锁时长
                        bool = lock.tryLock(lockRun.waitTime(), lockRun.timeUnit());
                    }
                } else {
                    // 1.2、尝试加锁无需等待
                    bool = lock.tryLock();
                }
            } catch (InterruptedException e) {
                log.error("尝试加锁中断，加锁失败。key={} waitTime={} lockTime={} unit={} message={}", key, lockRun.waitTime(),
                        lockRun.lockTime(), lockRun.timeUnit(), e.getMessage(), e);
            }
            log.info("尝试加锁完成。key={} waitTime={} lockTime={} unit={} bool={}", key, lockRun.waitTime(), lockRun.lockTime(),
                    lockRun.timeUnit(), bool);
            if (!bool) {
                // 加锁失败，抛出异常
                throw new YlrException(lockRun.message().getMsg());
            }
        } else {
            // 2、强制加锁
            if (lockRun.lockTime() > 0) {
                // 2.1、加锁时长有值
                lock.lock(lockRun.lockTime(), lockRun.timeUnit());
            } else {
                // 2.2、不设置加锁时长，使用默认30秒加锁时长
                lock.lock();
            }
            log.info("加锁完成。key={} waitTime={} lockTime={} unit={}", key, lockRun.waitTime(), lockRun.lockTime(),
                    lockRun.timeUnit());
        }
        try {
            // 执行目标方法
            return pjp.proceed();
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
                log.info("解锁成功");
            }
        }
    }
}
